{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Bioinformatics Aligrithms\n",
    "> Bioinformatics Algorithms Design and Implementation in Python\n",
    "\n",
    "> Miguel Rocha University of Minho, Braga, Portugal\n",
    "\n",
    "> Pedro G. Ferreira Ipatimup/i3S, Porto, Portugal\n",
    "\n",
    "## Pattern Recognition of Sequence\n",
    "### Approach\n",
    "*  Naive Algorithm\n",
    "\n",
    "### Jobs\n",
    "* Enzyme Binding Site with ligands\n",
    "* Promoter, Enhancer, Transcription Factor and so on of DNA\n",
    "* Repeated Segments\n",
    "\n",
    "### Patterns\n",
    "* Fixed Pattern $\\rightarrow$ 算法复杂度\n",
    "* 灵活模式 $\\rightarrow$ 正则表达式\n",
    "\n",
    "####  Naive Algorithm for Fixed Pattern Finding\n",
    "Example1: 匹配句子中的某一单词\n",
    "##### Description\n",
    "A naive approach to search for the occurrence of a pattern p (of length k) in a sequence s (of length N>k) is to consider __all possible sub-sequences of s__, of size k, and compare each of those, position by position, to p. Once a mismatch is found, we can move to test the next sub-sequence.\n",
    "\n",
    "If the sub-sequence matches the pattern in all positions, an occurrence of p is found. If the purpose is to find all occurrences of p in s, we must continue even in case of success, while if it is only required to find the first occurrence (or simply if the pattern occurs), we can stop when the first sub-sequence matches p.\n",
    "\n",
    "Both variants are implemented in the Python functions provided in the following code block.\n",
    "\n",
    "The ```search_first_occ``` function uses an outer while cycle that finishes when the pattern is found, or the last sub-sequence is tested. The function returns the position of the first occurrence of the pattern, or if the pattern does not occur returns −1.\n",
    "\n",
    "The ```search_all_occurrences``` function uses a for cycle that tests for all possible subsequences (note that there are N − k + 1 possible positions where the pattern may occur), and returns a list with all initial positions of the pattern’s occurrences (the list is empty if the pattern does not occur)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "def search_first_occ(seq, pattern): \n",
    "    found = False\n",
    "    i=0\n",
    "    while i <= len(seq)-len(pattern) and not found:\n",
    "        j=0\n",
    "        while j < len(pattern) and pattern[j]==seq[i+j]:\n",
    "            j=j+1\n",
    "        if j == len(pattern): found = True \n",
    "        else: i += 1\n",
    "    if found: return i\n",
    "    else: return -1\n",
    "    \n",
    "\n",
    "def search_all_occurrences(seq, pattern): \n",
    "    res = []\n",
    "    for i in range(len(seq)-len(pattern)+1):\n",
    "        j=0\n",
    "        while j < len(pattern) and pattern[j]==seq[i+j]:\n",
    "            j=j+1\n",
    "        if j == len(pattern):\n",
    "            res.append(i) \n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3\n",
      "-1\n",
      "[4, 11]\n",
      "\n",
      "3\n",
      "-1\n",
      "[4, 11]\n"
     ]
    }
   ],
   "source": [
    "seqDNA = \"ATAGAATAGATAATAGTC\"\n",
    "print(search_first_occ(seqDNA, \"GAAT\")) \n",
    "print(search_first_occ(seqDNA, \"TATA\")) \n",
    "print(search_all_occurrences(seqDNA, \"AAT\"))\n",
    "print()\n",
    "print(seqDNA.find(\"GAAT\"))\n",
    "print(seqDNA.find(\"TATA\"))\n",
    "print(\n",
    "    sorted(\n",
    "        set(\n",
    "            filter(\n",
    "                lambda x: x != -1, (\n",
    "                    seqDNA.find(\"AAT\",i) for i in range(len(seqDNA))\n",
    "                )\n",
    "            )\n",
    "        )\n",
    "    )\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "3"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"TAGAATAGATAATAGTC\".find(\"AAT\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Help on method_descriptor:\n",
      "\n",
      "find(...)\n",
      "    S.find(sub[, start[, end]]) -> int\n",
      "    \n",
      "    Return the lowest index in S where substring sub is found,\n",
      "    such that sub is contained within S[start:end].  Optional\n",
      "    arguments start and end are interpreted as in slice notation.\n",
      "    \n",
      "    Return -1 on failure.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "help(str.find)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "####   Heuristic Algorithm: _Boyer-Moore_\n",
    "> 跳跃，减少无用步数，同时考虑潜在可能\n",
    "\n",
    "\n",
    "ABC __D__ EACD \n",
    "\n",
    "EAC __D__\n",
    "\n",
    "ABCD __EACD__\n",
    "\n",
    "0OO0 __EACD__\n",
    "\n",
    "从左到右，从末尾往前匹配\n",
    "##### 坏字符规则\n",
    "\n",
    "##### 好后缀规则\n",
    "\n",
    "__注意，位置从0开始计算__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class BoyerMoore:\n",
    "    # initialized object of this class \n",
    "    def __init__(self, alphabet, pattern):\n",
    "        self.alphabet = alphabet\n",
    "        self.pattern = pattern\n",
    "        self.preprocess()\n",
    "        \n",
    "    def preprocess(self):\n",
    "        self.process_bcr()\n",
    "        self.process_gsr()\n",
    "    \n",
    "    def process_bcr(self):\n",
    "        self.occ = {}\n",
    "        # 初始化全集字典\n",
    "        for symb in self.alphabet:\n",
    "            self.occ[symb] = -1\n",
    "        # 更新全集字典，存储各个字符在Pattern中最后出现的位置\n",
    "        for j in range(len(self.pattern)):\n",
    "            c = self.pattern[j]\n",
    "            self.occ[c] = j\n",
    "    \n",
    "    def process_gsr( self ):\n",
    "        # 初始化两个Pattern列表用以记录Pattern中的重复字符\n",
    "        # 初始化Pattern列表 1\n",
    "        self.f = [0] * (len(self.pattern)+1)\n",
    "        # 初始化Pattern列表 2\n",
    "        self.s = [0] * (len(self.pattern)+1)\n",
    "        i = len(self.pattern)\n",
    "        j = len(self.pattern)+1\n",
    "        # 定义Pattern列表 1的最后元素为pattern长度+1\n",
    "        self.f[i] = j\n",
    "        # 查找Pattern中非重复字符的位置差并存于列表2中，\n",
    "        while i>0:\n",
    "            while j<= len(self.pattern) and self.pattern[i-1]!=self.pattern[j-1]:\n",
    "                if self.s[j] == 0: self.s[j] = j-i;\n",
    "                # 修正Index\n",
    "                j = self.f[j]\n",
    "            i -= 1\n",
    "            j -= 1\n",
    "            self.f[i] = j\n",
    "        j = self.f[0]\n",
    "        for i in range(len(self.pattern)):\n",
    "            if self.s[i] == 0: self.s[i] = j\n",
    "            if i == j: j = self.f[j]\n",
    "\n",
    "    def search_pattern(self, text):\n",
    "        res = []\n",
    "        i=0\n",
    "        while i <= len(text) - len(self.pattern):\n",
    "            j= len(self.pattern)- 1\n",
    "            while j>=0 and self.pattern[j]==text[j+i]: j -= 1\n",
    "            if (j<0):\n",
    "                res.append(i)\n",
    "                i += self.s[0]\n",
    "            else:\n",
    "                c = text[j+i]\n",
    "                i += max(self.s[j+1], j-self.occ[c])\n",
    "        return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[5, 13, 23, 37]\n"
     ]
    }
   ],
   "source": [
    "bm = BoyerMoore(\"ACTG\", \"ACCA\")\n",
    "print(bm.search_pattern(\"ATAGAACCAATGAACCATGATGAACCATGGATACCCAACCACC\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "pattern=\"ACCA\"\n",
    "f = [0] * (len(pattern)+1)\n",
    " # 初始化Pattern列表 2\n",
    "s = [0] * (len(pattern)+1)\n",
    "i = len(pattern)\n",
    "j = len(pattern)+1\n",
    "# 定义Pattern列表 1的最后元素为pattern长度+1\n",
    "f[i] = j"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "([0, 0, 0, 0, 5], [0, 0, 0, 0, 0], 4, 5)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f,s,i,j"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "False\n",
      "C A\n"
     ]
    }
   ],
   "source": [
    "print(j<= 4 and pattern[3-1]!=pattern[4-1])\n",
    "print(pattern[3-1],pattern[4-1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 查找Pattern中非重复字符的位置差并存于列表2中，\n",
    "while i>0:\n",
    "    '''\n",
    "     [A, C, C, A   ]\n",
    "      0  1  2  3\n",
    "      |  |  |  |\n",
    "    f[0, 0, 0, 4, 5]\n",
    "    i       ^  |\n",
    "    j          ^  |\n",
    "    s[0, 0, 0, 0, 0]\n",
    "    '''\n",
    "    while j<= len(pattern) and pattern[i-1]!=pattern[j-1]:\n",
    "        if s[j] == 0: s[j] = j-i;\n",
    "        # 修正Index\n",
    "        j = f[j]\n",
    "    i -= 1\n",
    "    j -= 1\n",
    "    f[i] = j"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "([3, 4, 4, 4, 5], [0, 0, 0, 0, 1], 0, 3)"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "f,s,i,j"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "def find_all_occurrences_re(seq, pat):\n",
    "    from re import finditer\n",
    "    mos = finditer(pat, seq)\n",
    "    res = []\n",
    "    for x in mos:\n",
    "        res.append(x.span()[0])\n",
    "    return res\n",
    "\n",
    "\n",
    "def find_all_overlap(seq, pat, mode=1):\n",
    "    if mode == 1:\n",
    "        return find_all_occurrences_re(seq, \"(?=\" + pat + \")\")\n",
    "    else:\n",
    "        return find_all_occurrences_re(seq, pat)\n",
    "\n",
    "\n",
    "def test(seq, pat, mode):\n",
    "    all_ov = find_all_overlap(seq, pat, mode)\n",
    "    if len(all_ov) > 0:\n",
    "        print(\"Pattern found in positions: \", all_ov)\n",
    "    else:\n",
    "        print(\"Pattern not found\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "import re"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['CC']"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "re.findall(\"CC\",\"ATCCCGTCG\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Pattern found in positions:  [2, 3]\n",
      "Pattern found in positions:  [2]\n"
     ]
    }
   ],
   "source": [
    "test('ATCCCGTCG','CC', 1)\n",
    "test('ATCCCGTCG','CC',0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sequence Alignment\n",
    "> 与运筹学、最优化相关问题的全局最优、局部最优相似\n",
    "\n",
    "* global alignment\n",
    "* local alignment\n",
    "* gaps\n",
    "\n",
    "#### Definitions\n",
    "* 同源性\n",
    "* 相似性\n",
    "* 一致性?\n",
    "\n",
    "#### Application\n",
    "1. Global Alignment: 适用于非常相似且长度近似相等的序列\n",
    "2. Local Alignment: 适用于一些片段相似而另一些片段相异的序列\n",
    "\n",
    "#### Visualization\n",
    "1. Dotlet -> 寻找单个序列中的重复片段\n",
    "2. HeatMap -> SAME\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " TATATATT\n",
      "C        \n",
      "G        \n",
      "A ∗ ∗ ∗  \n",
      "T∗ ∗ ∗ ∗∗\n",
      "A ∗ ∗ ∗  \n",
      "T∗ ∗ ∗ ∗∗\n",
      "A ∗ ∗ ∗  \n",
      "G        \n"
     ]
    }
   ],
   "source": [
    "# 最简情况: 二元矩阵\n",
    "# From TEACHER\n",
    "#1. dot plot\n",
    "def create_mat(nrows, ncols):\n",
    "    #create a matrix filled with zero\n",
    "    mat = []\n",
    "    for i in range(nrows):\n",
    "        mat.append([])\n",
    "        for j in range(ncols):\n",
    "            mat[i].append(0)\n",
    "    return mat\n",
    "\n",
    "def dotplot(seq1, seq2):\n",
    "    #construct the dot matrix\n",
    "    mat = create_mat(len(seq1), len(seq2))\n",
    "    for i in range(len(seq1)):\n",
    "        for j in range(len(seq2)):\n",
    "            if seq1[i] == seq2[j]:\n",
    "                mat[i][j] = 1\n",
    "    return mat\n",
    "\n",
    "def print_dotplot(mat, s1, s2):\n",
    "    #print dotplot matrix\n",
    "    import sys\n",
    "    sys.stdout.write(\" \" + s2+\"\\n\")\n",
    "    for i in range(len(mat)):\n",
    "        sys.stdout.write(s1[i])\n",
    "        for j in range(len(mat[i])):\n",
    "            if mat[i][j] >= 1:\n",
    "                sys.stdout.write(\"∗\")\n",
    "            else:\n",
    "                sys.stdout.write(\" \")\n",
    "        sys.stdout.write(\"\\n\")\n",
    "        \n",
    "s1 = \"CGATATAG\"\n",
    "s2 = \"TATATATT\"\n",
    "mat1 = dotplot(s1, s2)\n",
    "print_dotplot(mat1, s1, s2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Since, in many cases, this simple algorithm leads to much noise, it is common to ﬁlter the results. ___One of the possible strategies is to consider a window around each position, and count the number of matching characters for each sequence in such neighborhood.___ In this case, we only ﬁll a given cell if, within this neighborhood, the number of matching characters exceeds a given parameter, typically named as stringency. A function implementing this strategy is given below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " TATATAGTAT\n",
      "C          \n",
      "G          \n",
      "A          \n",
      "T  ∗       \n",
      "A   ∗      \n",
      "T  ∗ ∗     \n",
      "A   ∗ ∗    \n",
      "G  ∗       \n",
      "A          \n",
      "T          \n",
      "T          \n"
     ]
    }
   ],
   "source": [
    "def extended_dotplot (seq1, seq2, window, stringency):\n",
    "    mat = create_mat(len(seq1), len(seq2))\n",
    "    start = int(window/2)\n",
    "    for i in range(start,len(seq1)-start):\n",
    "        for j in range(start, len(seq2)-start):\n",
    "            matches = 0\n",
    "            l = j-start\n",
    "            for k in range(i-start, i+start+1):\n",
    "                if seq1[k] == seq2[l]: matches += 1\n",
    "                l += 1\n",
    "                if matches >= stringency: mat[i][j] = 1\n",
    "    return mat\n",
    "\n",
    "s1 = \"CGATATAGATT\"\n",
    "s2 = \"TATATAGTAT\"\n",
    "mat2 = extended_dotplot(s1, s2, 5, 4)\n",
    "print_dotplot(mat2, s1, s2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import seaborn as sns\n",
    "from collections import OrderedDict"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "def dotlet_plot(seq1, seq2, window, stringency):\n",
    "    # assert window>=stringency, \"Invalid Input ! window should >= stringency\"\n",
    "    # seq_index = [i for i in seq]\n",
    "    df = pd.DataFrame([[0]*len(seq1) for i in seq2])# , columns=seq_index, index=seq_index)\n",
    "    '''\n",
    "    for i in range(len(seq1)):\n",
    "        for j in range(len(seq2)):\n",
    "            if seq1[i] == seq2[j]:\n",
    "                df[i][j] = 1\n",
    "    '''\n",
    "    start = int(window/2)\n",
    "    for i in range(start,len(seq1)-start):\n",
    "        for j in range(start, len(seq2)-start):\n",
    "            '''\n",
    "            In this case, we only fill a given cell if,\n",
    "            within this neighborhood, the number of matching\n",
    "            characters exceeds a given parameter(stringency)\n",
    "            '''\n",
    "            matches = 0\n",
    "            # Creact a window\n",
    "            l = j-start # ROW\n",
    "            for k in range(i-start, i+start+1): # COL\n",
    "                '''\n",
    "                i-start -> i+start+1\n",
    "                length: 2*start + 1\n",
    "                => \n",
    "                '''\n",
    "                if seq1[k] == seq2[l]:\n",
    "                    matches += 1\n",
    "                l += 1 # ROW\n",
    "                if matches >= stringency: # and seq1[i] == seq2[j]:\n",
    "                    df[i][j] = 1\n",
    "                    break\n",
    "    return df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.axes._subplots.AxesSubplot at 0x1a2f408e080>"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "sns.heatmap(dotlet_plot(s1,s2,5,4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.axes._subplots.AxesSubplot at 0x1a2f4413630>"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "sns.heatmap(dotlet_plot(\"CCATCTATCGGATA\",\"ATCTATCGGATAC\",5,4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Improvment\n",
    "* 目标函数\n",
    "    * 替换矩阵 $round(2\\,\\log_{2}^{\\frac{P(a,b)}{P(a)\\,P(b)}})$\n",
    "    * 空位罚分\n",
    "\n",
    "> $P(ab) = P(a|b)\\,P(b) = P(b|a)\\,P(a) \\Rightarrow \\frac{P(a,b)}{P(a)\\,P(b)} = \\frac{P(a|b)}{P(a)} = \\frac{P(b|a)}{P(b)}$\n",
    "\n",
    "\n",
    "\n",
    "* BLOSUM62 MATRIX\n",
    "\n",
    "#### Dynamic Programming\n",
    "* Needleman-Wunsch\n",
    "* Smith-Waterman"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "from Bio.SubsMat import MatrixInfo\n",
    "matrix = MatrixInfo.blosum62"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{('W', 'F'): 1,\n",
       " ('L', 'R'): -2,\n",
       " ('S', 'P'): -1,\n",
       " ('V', 'T'): 0,\n",
       " ('Q', 'Q'): 5,\n",
       " ('N', 'A'): -2,\n",
       " ('Z', 'Y'): -2,\n",
       " ('W', 'R'): -3,\n",
       " ('Q', 'A'): -1,\n",
       " ('S', 'D'): 0,\n",
       " ('H', 'H'): 8,\n",
       " ('S', 'H'): -1,\n",
       " ('H', 'D'): -1,\n",
       " ('L', 'N'): -3,\n",
       " ('W', 'A'): -3,\n",
       " ('Y', 'M'): -1,\n",
       " ('G', 'R'): -2,\n",
       " ('Y', 'I'): -1,\n",
       " ('Y', 'E'): -2,\n",
       " ('B', 'Y'): -3,\n",
       " ('Y', 'A'): -2,\n",
       " ('V', 'D'): -3,\n",
       " ('B', 'S'): 0,\n",
       " ('Y', 'Y'): 7,\n",
       " ('G', 'N'): 0,\n",
       " ('E', 'C'): -4,\n",
       " ('Y', 'Q'): -1,\n",
       " ('Z', 'Z'): 4,\n",
       " ('V', 'A'): 0,\n",
       " ('C', 'C'): 9,\n",
       " ('M', 'R'): -1,\n",
       " ('V', 'E'): -2,\n",
       " ('T', 'N'): 0,\n",
       " ('P', 'P'): 7,\n",
       " ('V', 'I'): 3,\n",
       " ('V', 'S'): -2,\n",
       " ('Z', 'P'): -1,\n",
       " ('V', 'M'): 1,\n",
       " ('T', 'F'): -2,\n",
       " ('V', 'Q'): -2,\n",
       " ('K', 'K'): 5,\n",
       " ('P', 'D'): -1,\n",
       " ('I', 'H'): -3,\n",
       " ('I', 'D'): -3,\n",
       " ('T', 'R'): -1,\n",
       " ('P', 'L'): -3,\n",
       " ('K', 'G'): -2,\n",
       " ('M', 'N'): -2,\n",
       " ('P', 'H'): -2,\n",
       " ('F', 'Q'): -3,\n",
       " ('Z', 'G'): -2,\n",
       " ('X', 'L'): -1,\n",
       " ('T', 'M'): -1,\n",
       " ('Z', 'C'): -3,\n",
       " ('X', 'H'): -1,\n",
       " ('D', 'R'): -2,\n",
       " ('B', 'W'): -4,\n",
       " ('X', 'D'): -1,\n",
       " ('Z', 'K'): 1,\n",
       " ('F', 'A'): -2,\n",
       " ('Z', 'W'): -3,\n",
       " ('F', 'E'): -3,\n",
       " ('D', 'N'): 1,\n",
       " ('B', 'K'): 0,\n",
       " ('X', 'X'): -1,\n",
       " ('F', 'I'): 0,\n",
       " ('B', 'G'): -1,\n",
       " ('X', 'T'): 0,\n",
       " ('F', 'M'): 0,\n",
       " ('B', 'C'): -3,\n",
       " ('Z', 'I'): -3,\n",
       " ('Z', 'V'): -2,\n",
       " ('S', 'S'): 4,\n",
       " ('L', 'Q'): -2,\n",
       " ('W', 'E'): -3,\n",
       " ('Q', 'R'): 1,\n",
       " ('N', 'N'): 6,\n",
       " ('W', 'M'): -1,\n",
       " ('Q', 'C'): -3,\n",
       " ('W', 'I'): -3,\n",
       " ('S', 'C'): -1,\n",
       " ('L', 'A'): -1,\n",
       " ('S', 'G'): 0,\n",
       " ('L', 'E'): -3,\n",
       " ('W', 'Q'): -2,\n",
       " ('H', 'G'): -2,\n",
       " ('S', 'K'): 0,\n",
       " ('Q', 'N'): 0,\n",
       " ('N', 'R'): 0,\n",
       " ('H', 'C'): -3,\n",
       " ('Y', 'N'): -2,\n",
       " ('G', 'Q'): -2,\n",
       " ('Y', 'F'): 3,\n",
       " ('C', 'A'): 0,\n",
       " ('V', 'L'): 1,\n",
       " ('G', 'E'): -2,\n",
       " ('G', 'A'): 0,\n",
       " ('K', 'R'): 2,\n",
       " ('E', 'D'): 2,\n",
       " ('Y', 'R'): -2,\n",
       " ('M', 'Q'): 0,\n",
       " ('T', 'I'): -1,\n",
       " ('C', 'D'): -3,\n",
       " ('V', 'F'): -1,\n",
       " ('T', 'A'): 0,\n",
       " ('T', 'P'): -1,\n",
       " ('B', 'P'): -2,\n",
       " ('T', 'E'): -1,\n",
       " ('V', 'N'): -3,\n",
       " ('P', 'G'): -2,\n",
       " ('M', 'A'): -1,\n",
       " ('K', 'H'): -1,\n",
       " ('V', 'R'): -3,\n",
       " ('P', 'C'): -3,\n",
       " ('M', 'E'): -2,\n",
       " ('K', 'L'): -2,\n",
       " ('V', 'V'): 4,\n",
       " ('M', 'I'): 1,\n",
       " ('T', 'Q'): -1,\n",
       " ('I', 'G'): -4,\n",
       " ('P', 'K'): -1,\n",
       " ('M', 'M'): 5,\n",
       " ('K', 'D'): -1,\n",
       " ('I', 'C'): -1,\n",
       " ('Z', 'D'): 1,\n",
       " ('F', 'R'): -3,\n",
       " ('X', 'K'): -1,\n",
       " ('Q', 'D'): 0,\n",
       " ('X', 'G'): -1,\n",
       " ('Z', 'L'): -3,\n",
       " ('X', 'C'): -2,\n",
       " ('Z', 'H'): 0,\n",
       " ('B', 'L'): -4,\n",
       " ('B', 'H'): 0,\n",
       " ('F', 'F'): 6,\n",
       " ('X', 'W'): -2,\n",
       " ('B', 'D'): 4,\n",
       " ('D', 'A'): -2,\n",
       " ('S', 'L'): -2,\n",
       " ('X', 'S'): 0,\n",
       " ('F', 'N'): -3,\n",
       " ('S', 'R'): -1,\n",
       " ('W', 'D'): -4,\n",
       " ('V', 'Y'): -1,\n",
       " ('W', 'L'): -2,\n",
       " ('H', 'R'): 0,\n",
       " ('W', 'H'): -2,\n",
       " ('H', 'N'): 1,\n",
       " ('W', 'T'): -2,\n",
       " ('T', 'T'): 5,\n",
       " ('S', 'F'): -2,\n",
       " ('W', 'P'): -4,\n",
       " ('L', 'D'): -4,\n",
       " ('B', 'I'): -3,\n",
       " ('L', 'H'): -3,\n",
       " ('S', 'N'): 1,\n",
       " ('B', 'T'): -1,\n",
       " ('L', 'L'): 4,\n",
       " ('Y', 'K'): -2,\n",
       " ('E', 'Q'): 2,\n",
       " ('Y', 'G'): -3,\n",
       " ('Z', 'S'): 0,\n",
       " ('Y', 'C'): -2,\n",
       " ('G', 'D'): -1,\n",
       " ('B', 'V'): -3,\n",
       " ('E', 'A'): -1,\n",
       " ('Y', 'W'): 2,\n",
       " ('E', 'E'): 5,\n",
       " ('Y', 'S'): -2,\n",
       " ('C', 'N'): -3,\n",
       " ('V', 'C'): -1,\n",
       " ('T', 'H'): -2,\n",
       " ('P', 'R'): -2,\n",
       " ('V', 'G'): -3,\n",
       " ('T', 'L'): -1,\n",
       " ('V', 'K'): -2,\n",
       " ('K', 'Q'): 1,\n",
       " ('R', 'A'): -1,\n",
       " ('I', 'R'): -3,\n",
       " ('T', 'D'): -1,\n",
       " ('P', 'F'): -4,\n",
       " ('I', 'N'): -3,\n",
       " ('K', 'I'): -3,\n",
       " ('M', 'D'): -3,\n",
       " ('V', 'W'): -3,\n",
       " ('W', 'W'): 11,\n",
       " ('M', 'H'): -2,\n",
       " ('P', 'N'): -2,\n",
       " ('K', 'A'): -1,\n",
       " ('M', 'L'): 2,\n",
       " ('K', 'E'): 1,\n",
       " ('Z', 'E'): 4,\n",
       " ('X', 'N'): -1,\n",
       " ('Z', 'A'): -1,\n",
       " ('Z', 'M'): -1,\n",
       " ('X', 'F'): -1,\n",
       " ('K', 'C'): -3,\n",
       " ('B', 'Q'): 0,\n",
       " ('X', 'B'): -1,\n",
       " ('B', 'M'): -3,\n",
       " ('F', 'C'): -2,\n",
       " ('Z', 'Q'): 3,\n",
       " ('X', 'Z'): -1,\n",
       " ('F', 'G'): -3,\n",
       " ('B', 'E'): 1,\n",
       " ('X', 'V'): -1,\n",
       " ('F', 'K'): -3,\n",
       " ('B', 'A'): -2,\n",
       " ('X', 'R'): -1,\n",
       " ('D', 'D'): 6,\n",
       " ('W', 'G'): -2,\n",
       " ('Z', 'F'): -3,\n",
       " ('S', 'Q'): 0,\n",
       " ('W', 'C'): -2,\n",
       " ('W', 'K'): -3,\n",
       " ('H', 'Q'): 0,\n",
       " ('L', 'C'): -1,\n",
       " ('W', 'N'): -4,\n",
       " ('S', 'A'): 1,\n",
       " ('L', 'G'): -4,\n",
       " ('W', 'S'): -3,\n",
       " ('S', 'E'): 0,\n",
       " ('H', 'E'): 0,\n",
       " ('S', 'I'): -2,\n",
       " ('H', 'A'): -2,\n",
       " ('S', 'M'): -1,\n",
       " ('Y', 'L'): -1,\n",
       " ('Y', 'H'): 2,\n",
       " ('Y', 'D'): -3,\n",
       " ('E', 'R'): 0,\n",
       " ('X', 'P'): -2,\n",
       " ('G', 'G'): 6,\n",
       " ('G', 'C'): -3,\n",
       " ('E', 'N'): 0,\n",
       " ('Y', 'T'): -2,\n",
       " ('Y', 'P'): -3,\n",
       " ('T', 'K'): -1,\n",
       " ('A', 'A'): 4,\n",
       " ('P', 'Q'): -1,\n",
       " ('T', 'C'): -1,\n",
       " ('V', 'H'): -3,\n",
       " ('T', 'G'): -2,\n",
       " ('I', 'Q'): -3,\n",
       " ('Z', 'T'): -1,\n",
       " ('C', 'R'): -3,\n",
       " ('V', 'P'): -2,\n",
       " ('P', 'E'): -1,\n",
       " ('M', 'C'): -1,\n",
       " ('K', 'N'): 0,\n",
       " ('I', 'I'): 4,\n",
       " ('P', 'A'): -1,\n",
       " ('M', 'G'): -3,\n",
       " ('T', 'S'): 1,\n",
       " ('I', 'E'): -3,\n",
       " ('P', 'M'): -2,\n",
       " ('M', 'K'): -1,\n",
       " ('I', 'A'): -1,\n",
       " ('P', 'I'): -3,\n",
       " ('R', 'R'): 5,\n",
       " ('X', 'M'): -1,\n",
       " ('L', 'I'): 2,\n",
       " ('X', 'I'): -1,\n",
       " ('Z', 'B'): 1,\n",
       " ('X', 'E'): -1,\n",
       " ('Z', 'N'): 0,\n",
       " ('X', 'A'): 0,\n",
       " ('B', 'R'): -1,\n",
       " ('B', 'N'): 3,\n",
       " ('F', 'D'): -3,\n",
       " ('X', 'Y'): -1,\n",
       " ('Z', 'R'): 0,\n",
       " ('F', 'H'): -1,\n",
       " ('B', 'F'): -3,\n",
       " ('F', 'L'): 0,\n",
       " ('X', 'Q'): -1,\n",
       " ('B', 'B'): 4}"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "aa_li = [i for i in 'ACDEFGHIKLMNPQRSTVWY']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "l = len(aa_li)\n",
    "a = pd.DataFrame([[0]*l for i in aa_li])\n",
    "for i in range(l):\n",
    "    for j in range(l):\n",
    "        try:\n",
    "            a[i][j] = matrix[(aa_li[i],aa_li[j])]\n",
    "        except KeyError:\n",
    "            a[i][j] = matrix[(aa_li[j],aa_li[i])]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.axes._subplots.AxesSubplot at 0x1a2f459db00>"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 720x576 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig = plt.figure(figsize=(10, 8))\n",
    "a.columns=aa_li\n",
    "a.index=aa_li\n",
    "sns.heatmap(a,annot=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Needleman-Wunsch\n",
    "def score_pos (c1, c2, sm, g):\n",
    "    if c1 == \"−\" or c2==\"−\":\n",
    "        return g\n",
    "    else:\n",
    "        if isinstance(sm, dict):\n",
    "            return sm[c1+c2]\n",
    "        else:\n",
    "            return sm[c1][c2]\n",
    "    \n",
    "    \n",
    "def read_submat_file (filename): \n",
    "    sm = {}\n",
    "    f = open(filename , \"r\") \n",
    "    line = f.readline() \n",
    "    tokens = line.split(\"\\t\") \n",
    "    ns = len(tokens)\n",
    "    alphabet = []\n",
    "    for i in range(0, ns):\n",
    "        alphabet.append(tokens[i][0]) \n",
    "    for i in range(0,ns):\n",
    "        line = f.readline();\n",
    "        tokens = line.split(\"\\t\");\n",
    "        for j in range(0, len(tokens)):\n",
    "            k = alphabet[i]+alphabet[j]\n",
    "            sm[k] = int (tokens[j]) \n",
    "    return sm\n",
    "\n",
    "\n",
    "def needleman_Wunsch (seq1, seq2, sm, g):\n",
    "    S = [[0]]\n",
    "    T = [[0]]\n",
    "    ## initialize gaps’ row\n",
    "    for j in range(1, len(seq2)+1):\n",
    "        S[0].append(g * j)\n",
    "        T[0].append(3)\n",
    "    ## initialize gaps’ column\n",
    "    for i in range(1, len(seq1)+1):\n",
    "        S.append([g * i])\n",
    "        T.append([2])\n",
    "    ## apply the recurrence relation to fill the remaining of the matrix\n",
    "    for i in range(0, len(seq1)):\n",
    "        for j in range(len(seq2)):\n",
    "            s1 = S[i][j] + score_pos(seq1[i],seq2[j],sm,g);\n",
    "            s2 = S[i][j+1] + g\n",
    "            s3 = S[i+1][j] + g\n",
    "            S[i+1].append(max(s1, s2, s3))\n",
    "            T[i+1].append(max3t(s1, s2, s3))\n",
    "    return (S, T)\n",
    "\n",
    "def max3t (v1, v2, v3):\n",
    "    if v1 > v2:\n",
    "        if v1 > v3: return 1\n",
    "        else: return 3\n",
    "    else:\n",
    "        if v2 > v3: return 2\n",
    "        else: return 3\n",
    "\n",
    "\n",
    "def recover_align (T, seq1, seq2):\n",
    "    res = [\"\", \"\"]\n",
    "    i = len(seq1)\n",
    "    j = len(seq2)\n",
    "    while i>0 or j>0:\n",
    "        if T[i][j]==1:\n",
    "            res[0] = seq1[i-1] + res[0]\n",
    "            res[1] = seq2[j-1] + res[1]\n",
    "            i -= 1\n",
    "            j -= 1\n",
    "        elif T[i][j] == 3:\n",
    "            res [0] = \"−\" + res [0]\n",
    "            res[1] = seq2[j-1] + res[1]\n",
    "            j -= 1\n",
    "        else:\n",
    "            res[0] = seq1[i-1] + res[0]\n",
    "            res [1] = \"−\" + res [1]\n",
    "            i -= 1\n",
    "    return res\n",
    "\n",
    "\n",
    "def print_mat(mat):\n",
    "    for i in range(0, len(mat)):\n",
    "        print(mat[i])\n",
    "\n",
    "def test_global_alig(sm):\n",
    "    # sm = read_submat_file(\"blosum62.mat\") \n",
    "    seq1 = \"PHSWG\"\n",
    "    seq2 = \"HGWAG\"\n",
    "    res = needleman_Wunsch(seq1, seq2, sm, -8)\n",
    "    S = res [0]\n",
    "    T = res [1]\n",
    "    print(\"Score of optimal alignment:\", S[len(seq1)][len(seq2)]) \n",
    "    print_mat(S)\n",
    "    print_mat(T)\n",
    "    alig = recover_align(T, seq1, seq2)\n",
    "    print(alig[0]) \n",
    "    print(alig[1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Score of optimal alignment: 9\n",
      "[0, -8, -16, -24, -32, -40]\n",
      "[-8, -2, -10, -18, -25, -33]\n",
      "[-16, 0, -4, -12, -20, -27]\n",
      "[-24, -8, 0, -7, -11, -19]\n",
      "[-32, -16, -8, 11, 3, -5]\n",
      "[-40, -24, -10, 3, 11, 9]\n",
      "[0, 3, 3, 3, 3, 3]\n",
      "[2, 1, 3, 3, 1, 3]\n",
      "[2, 1, 1, 3, 3, 1]\n",
      "[2, 2, 1, 1, 1, 3]\n",
      "[2, 2, 2, 1, 3, 3]\n",
      "[2, 2, 1, 2, 1, 1]\n",
      "PHSW−G\n",
      "−HGWAG\n"
     ]
    }
   ],
   "source": [
    "test_global_alig(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Smith-Waterman\n",
    "def smith_Waterman (seq1, seq2, sm, g): \n",
    "    S = [[0]]\n",
    "    T = [[0]]\n",
    "    maxscore = 0\n",
    "    for j in range(1, len(seq2)+1):\n",
    "        S[0].append(0)\n",
    "        T[0].append(0)\n",
    "    for i in range(1, len(seq1)+1):\n",
    "        S.append([0])\n",
    "        T.append([0])\n",
    "    for i in range(0, len(seq1)):\n",
    "        for j in range(len(seq2)):\n",
    "            s1 = S[i][j] + score_pos (seq1[i], seq2[j], sm, g); \n",
    "            s2 = S[i][j+1] + g\n",
    "            s3 = S[i+1][j] + g\n",
    "            b = max(s1, s2, s3)\n",
    "            if b <= 0: \n",
    "                S[i+1].append(0) \n",
    "                T[i+1].append(0)\n",
    "            else:\n",
    "                S[i+1].append(b) \n",
    "                T[i+1].append(max3t(s1, s2, s3))\n",
    "                if b > maxscore: \n",
    "                    maxscore = b\n",
    "    return (S, T, maxscore)  \n",
    "\n",
    "def recover_align_local (S, T, seq1, seq2): \n",
    "    res = [\"\", \"\"]\n",
    "    i, j = max_mat(S) \n",
    "    while T[i][j]>0:\n",
    "        if T[i][j]==1:\n",
    "            res[0] = seq1[i-1] + res[0] \n",
    "            res[1] = seq2[j-1] + res[1]\n",
    "            i -= 1\n",
    "            j -= 1\n",
    "        elif T[i][j] == 3:\n",
    "            res [0] = \"−\" + res [0];\n",
    "            res[1] = seq2[j-1] + res[1]\n",
    "            j -= 1\n",
    "        elif T[i][j] == 2:\n",
    "            res[0] = seq1[i-1] + res[0]\n",
    "            res [1] = \"-\" + res [1]\n",
    "            i -= 1\n",
    "    return res\n",
    "\n",
    "def max_mat(mat):\n",
    "    maxval = mat [0][0]\n",
    "    maxrow = 0\n",
    "    maxcol = 0\n",
    "    for i in range(0,len(mat)):\n",
    "        for j in range(0, len(mat[i])):\n",
    "            if mat[i][j] > maxval:\n",
    "                maxval = mat[i][j]\n",
    "                maxrow = i\n",
    "                maxcol = j\n",
    "    return (maxrow,maxcol)\n",
    "\n",
    "def test_local_alig(sm):\n",
    "    # sm = read_submat_file(\"blosum62.mat\")\n",
    "    seq1 = \"HGWAG\"\n",
    "    seq2 = \"PHSWG\"\n",
    "    res = smith_Waterman(seq1, seq2, sm, -8)\n",
    "    S = res [0]\n",
    "    T = res [1]\n",
    "    print(\"Score of optimal alignment:\", res[2])\n",
    "    print_mat(S)\n",
    "    print_mat(T)\n",
    "    alinL= recover_align_local(S, T, seq1, seq2)\n",
    "    print(alinL[0])\n",
    "    print(alinL[1]) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Score of optimal alignment: 19\n",
      "[0, 0, 0, 0, 0, 0]\n",
      "[0, 0, 8, 0, 0, 0]\n",
      "[0, 0, 0, 8, 0, 6]\n",
      "[0, 0, 0, 0, 19, 11]\n",
      "[0, 0, 0, 1, 11, 19]\n",
      "[0, 0, 0, 0, 3, 17]\n",
      "[0, 0, 0, 0, 0, 0]\n",
      "[0, 0, 1, 0, 0, 0]\n",
      "[0, 0, 0, 1, 0, 1]\n",
      "[0, 0, 0, 0, 1, 3]\n",
      "[0, 0, 0, 1, 2, 1]\n",
      "[0, 0, 0, 0, 2, 1]\n",
      "HGW\n",
      "HSW\n"
     ]
    }
   ],
   "source": [
    "test_local_alig(a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### BLAST\n",
    "\n",
    "SIMPLE VERSION: \n",
    "* 匹配分数简化\n",
    "* 只考虑精确匹配，未考虑neighbor\n",
    "* \n",
    "\n",
    "> Hit $\\rightarrow$ HSP(high-scoring segment pair)\n",
    "\n",
    "$\\Downarrow$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "def best_alignment (db, query, w):\n",
    "    # Get words(list) of query sequence\n",
    "    m = build_map(query, w)\n",
    "    # Initialization\n",
    "    bestScore = -1.0\n",
    "    res = (0,0,0,0,0)\n",
    "    # Traversing\n",
    "    for k in range(0,len(db)):\n",
    "        bestSeq = hit_best_score(db[k], query, m, w)\n",
    "        if bestSeq != ():\n",
    "            score = bestSeq [3]\n",
    "            # Choose seq with better score or same score but better size among each db sequences\n",
    "            if score > bestScore or (score== bestScore and bestSeq[2] < res[2]):\n",
    "                bestScore = score\n",
    "                res = bestSeq[0], bestSeq[1], bestSeq[2], bestSeq[3], k\n",
    "    if bestScore < 0:\n",
    "        return ()\n",
    "    else:\n",
    "        return res\n",
    "\n",
    "def build_map (query, w):\n",
    "    res = {}\n",
    "    for i in range(len(query)-w+1):\n",
    "        subseq = query[i:i+w]\n",
    "        if subseq in res:\n",
    "            res[subseq].append(i)\n",
    "        else:\n",
    "            res[subseq] = [i]\n",
    "    return res\n",
    "\n",
    "def hit_best_score(seq, query, m, w):\n",
    "    # Get the word-index of db sequence\n",
    "    hits = get_hits(seq, m, w)\n",
    "    # Initialization\n",
    "    bestScore = -1.0\n",
    "    best = ()\n",
    "    # Traversing\n",
    "    for h in hits:\n",
    "        # \n",
    "        ext = extends_hit(seq, h, query, w)\n",
    "        score = ext[3] # matched count\n",
    "        # Choose seq with better score or same score but better size among each hits(extended)\n",
    "        if score > bestScore or (score== bestScore and ext[2] < best [2]):\n",
    "            bestScore = score\n",
    "            best = ext\n",
    "    return best\n",
    "\n",
    "def get_hits (seq, m, w):\n",
    "    '''\n",
    "    m：words列表\n",
    "    return (搜索序列中匹配上的下标,目标序列中匹配上的下标)\n",
    "    '''\n",
    "    res = [] # list of tuples\n",
    "    for i in range(len(seq)-w+1):\n",
    "        subseq = seq[i:i+w]\n",
    "        if subseq in m:\n",
    "            l = m[subseq]\n",
    "            for ind in l:\n",
    "                res.append( (ind,i) )\n",
    "    return res\n",
    "\n",
    "def extends_hit (seq, hit, query, w):\n",
    "    stq, sts = hit[0],hit[1]\n",
    "    ## move forward\n",
    "    matfw = 0 # matching count\n",
    "    k=0 # index count\n",
    "    bestk = 0 # record matched position\n",
    "    while 2*matfw >= k and stq+w+k < len(query) and sts+w+k < len(seq):\n",
    "        # 2*matfw: tolerance cutoff of matching%(decrease the unnecessary loop)\n",
    "        if query[stq+w+k] == seq[sts+w+k]:\n",
    "            matfw+=1\n",
    "            bestk = k+1\n",
    "        k += 1\n",
    "        if 2*matfw < k:\n",
    "            print(\"WARNING_1\", hit)\n",
    "    size = w + bestk\n",
    "    ## move backwards\n",
    "    k=0\n",
    "    matbw = 0\n",
    "    bestk = 0\n",
    "    while 2*matbw >= k and stq > k and sts > k:\n",
    "        if query[stq-k-1] == seq[sts-k-1]:\n",
    "            matbw+=1\n",
    "            bestk = k+1\n",
    "        k+=1\n",
    "        if 2*matbw < k:\n",
    "            print(\"WARNING_2\",hit)\n",
    "    size += bestk\n",
    "    return (stq-bestk, sts-bestk, size, w+matfw+matbw)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "WARNING_1 (0, 8)\n",
      "WARNING_1 (1, 3)\n",
      "WARNING_1 (1, 6)\n",
      "WARNING_2 (1, 6)\n",
      "WARNING_2 (5, 7)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(0, 2, 11, 10, 1)"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "best_alignment(['ATRAFSDGASDGS','ERASDFSDFZGDGATATA'], 'ASDFTDFZGDG', 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "seq1 = 'PHSWG'\n",
    "seq2 = 'HGWAG'\n",
    "gap = -8\n",
    "\n",
    "S = [[0]]\n",
    "T = [[0]]\n",
    "# initialize gaps'row\n",
    "for j in range(1, len(seq2)+1):\n",
    "    S[0].append(gap * j)\n",
    "    T[0].append(3)\n",
    "# initialize gaps'column\n",
    "for i in range(1, len(seq1)+1):\n",
    "    S.append([gap * i])\n",
    "    T.append([2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[0, -8, -16, -24, -32, -40], [-8], [-16], [-24], [-32], [-40]]"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[\n",
    "    [0, -8, -16, -24, -32, -40], \n",
    "    [-8], \n",
    "    [-16], \n",
    "    [-24], \n",
    "    [-32], \n",
    "    [-40]\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[[0, 3, 3, 3, 3, 3], [2], [2], [2], [2], [2]]"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[\n",
    "    [0, 3, 3, 3, 3, 3], \n",
    "    [2], \n",
    "    [2], \n",
    "    [2], \n",
    "    [2], \n",
    "    [2]\n",
    "]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.axes._subplots.AxesSubplot at 0x1a2f4a45ef0>"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "sns.heatmap([\n",
    "    [0, -8, -16, -24, -32, -40], \n",
    "    [-8, -2, -10, -18, -25, -33], \n",
    "    [-16, 0, -4, -12, -20, -27], \n",
    "    [-24, -8, 0, -7, -11, -19], \n",
    "    [-32, -16, -8, 11, 3, -5], \n",
    "    [-40, -24, -10, 3, 11, 9]\n",
    "],annot=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<matplotlib.axes._subplots.AxesSubplot at 0x1a2f4691198>"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "sns.heatmap([\n",
    "    [0, 3, 3, 3, 3, 3], \n",
    "    [2, 1, 3, 3, 1, 3], \n",
    "    [2, 1, 1, 3, 3, 1], \n",
    "    [2, 2, 1, 1, 1, 3], \n",
    "    [2, 2, 2, 1, 3, 3], \n",
    "    [2, 2, 1, 2, 1, 1]\n",
    "],annot=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "notes"
    },
    "tags": [
     "2019-10-07"
    ]
   },
   "source": [
    "### Multiple Sequence Alignment (MSA)\n",
    "* Global\n",
    "* Local $\\rightarrow$ Motif Discovery\n",
    "* pairwise++\n",
    "    * Multi-Dimension DP $\\rightarrow$ Large amount of calculation\n",
    "    * Progressive $\\rightarrow$ 基于相似序列通常具有进化相关性的假设 (e.g Clustal _Feng, Doolittle_)\n",
    "\n",
    "#### Progressive (Simple)\n",
    "1. Dist, Similarity Matrix\n",
    "2. Guide Tree\n",
    "3. Alignment\n",
    "\n",
    "```bash\n",
    "S1  P E E [V] T ...\n",
    "S2  P E E [V] A ...\n",
    "S3  P E E [A] A ...\n",
    "S4  P E E [L] A ...\n",
    "\n",
    "for S4: Score = (SM(V, L)*2 + SM(A, L)) / 3\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "ATGA−A\n",
      "AA−AT−\n",
      "6\n",
      "['G', '−']\n",
      "A\n",
      "ATGA−A\n",
      "ATGATA\n"
     ]
    }
   ],
   "source": [
    "class MyAlign:\n",
    "    def __init__(self , lseqs, al_type = \"protein\"):\n",
    "        self.listseqs = lseqs\n",
    "        self.al_type = al_type\n",
    "\n",
    "    def __len__(self): # number of columns\n",
    "        return len(self.listseqs[0])\n",
    "\n",
    "    def __getitem__(self, n):\n",
    "        if type(n) is tuple and len(n) ==2:\n",
    "            i, j = n\n",
    "            return self.listseqs[i][j]\n",
    "        elif type(n) is int: return self.listseqs[n]\n",
    "        return None\n",
    "\n",
    "    def __str__(self):\n",
    "        res = \"\"\n",
    "        for seq in self.listseqs:\n",
    "            res += \"\\n\" + seq\n",
    "        return res\n",
    "\n",
    "    def num_seqs(self):\n",
    "        return len(self.listseqs)\n",
    "\n",
    "    def column(self, indice): \n",
    "        res = []\n",
    "        for k in range(len(self.listseqs)):\n",
    "            res.append( self.listseqs[k][indice])\n",
    "        return res\n",
    "\n",
    "    def consensus (self):\n",
    "        cons = \"\"\n",
    "        for i in range(len(self)):\n",
    "            cont = {}\n",
    "            for k in range(len(self.listseqs)):\n",
    "                c = self.listseqs[k][i]\n",
    "                if c in cont:\n",
    "                    cont[c] = cont[c] + 1\n",
    "                else:\n",
    "                    cont[c] = 1\n",
    "            maximum = 0\n",
    "            cmax = None\n",
    "            for ke in cont.keys():\n",
    "                if ke != \"−\" and cont[ke] > maximum:\n",
    "                    maximum = cont[ke]\n",
    "                    cmax = ke\n",
    "            cons = cons + cmax\n",
    "        return cons\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    alig = MyAlign([\"ATGA−A\",\"AA−AT−\"], \"dna\")\n",
    "    print(alig)\n",
    "    print(len(alig))\n",
    "    print(alig.column(2))\n",
    "    print(alig[1,1])\n",
    "    print(alig[0])\n",
    "    print(alig.consensus())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "class PairWiseAligner:\n",
    "    '''\n",
    "    # Pairwise Alignment\n",
    "    * Global Alignment: Needleman-Wunsch\n",
    "    * Local Alignment: Smith-Waterman\n",
    "    '''\n",
    "\n",
    "    def __init__(self):\n",
    "        '''Set DefaultValues'''\n",
    "        self.gapSymbol = \"-\"\n",
    "        self.defaultValue = 0\n",
    "        self.newMatrix = {}\n",
    "\n",
    "    def __repr__(self):\n",
    "        '''Show the Content of Matrix'''\n",
    "        from numpy import array\n",
    "        return \"Matrix(S):\\n%s\\n\\nMatrix(T):\\n%s\\n\" % (array(self.S), array(self.T))\n",
    "\n",
    "    @property\n",
    "    def defaultMatrix(self):\n",
    "        '''Get Blosum Matrix Via BioPython'''\n",
    "        from Bio.SubsMat import MatrixInfo\n",
    "        matrix = MatrixInfo.blosum62\n",
    "        return matrix\n",
    "\n",
    "    def score_pos(self, c1, c2):\n",
    "        '''Get Score of particular position via given matrix'''\n",
    "        if c1 == self.gapSymbol or c2 == self.gapSymbol:\n",
    "            return self.gap\n",
    "        else:\n",
    "            try:\n",
    "                result = self.matrix[(c1, c2)]\n",
    "            except KeyError:\n",
    "                try:\n",
    "                    result = self.matrix[(c2, c1)]\n",
    "                except KeyError:\n",
    "                    print(\"Unexpected key for given matrix, return defaultValue.\")\n",
    "                    return self.defaultValue\n",
    "        return result\n",
    "\n",
    "    def max3t(v1, v2, v3):\n",
    "        '''Get the optimized direction'''\n",
    "        if v1 > v2:\n",
    "            if v1 > v3:\n",
    "                return 1\n",
    "            else:\n",
    "                return 3\n",
    "        else:\n",
    "            if v2 > v3:\n",
    "                return 2\n",
    "            else:\n",
    "                return 3\n",
    "\n",
    "    def max_mat(mat):\n",
    "        '''Get the row and col of optimized values'''\n",
    "        maxval = mat[0][0]\n",
    "        maxrow = 0\n",
    "        maxcol = 0\n",
    "        for i in range(0, len(mat)):\n",
    "            for j in range(0, len(mat[i])):\n",
    "                if mat[i][j] > maxval:\n",
    "                    maxval = mat[i][j]\n",
    "                    maxrow = i\n",
    "                    maxcol = j\n",
    "        return maxrow, maxcol\n",
    "\n",
    "    def needleman_Wunsch(self):\n",
    "        '''\n",
    "        Global Alignment\n",
    "        * Dynamic Programming\n",
    "        * optimized\n",
    "        '''\n",
    "        self.S = [[0]]  # Record the score\n",
    "        self.T = [[0]]  # Record the orientation\n",
    "        # initialize gaps'row\n",
    "        for j in range(1, len(self.seq2)+1):\n",
    "            self.S[0].append(self.gap * j)\n",
    "            self.T[0].append(3)\n",
    "        # initialize gaps'column\n",
    "        for i in range(1, len(self.seq1)+1):\n",
    "            self.S.append([self.gap * i])\n",
    "            self.T.append([2])\n",
    "        # apply the recurrence relation to fill the remaining of the matrix\n",
    "        for i in range(len(self.seq1)):\n",
    "            for j in range(len(self.seq2)):\n",
    "                s1 = self.S[i][j] + self.score_pos(self.seq1[i], self.seq2[j])\n",
    "                s2 = self.S[i][j+1] + self.gap\n",
    "                s3 = self.S[i+1][j] + self.gap\n",
    "                self.S[i+1].append(max(s1, s2, s3))\n",
    "                self.T[i+1].append(PairWiseAligner.max3t(s1, s2, s3))\n",
    "\n",
    "    def smith_Waterman(self):\n",
    "        '''\n",
    "        Local Alignment\n",
    "        * Dynamic Programming\n",
    "        * optimized\n",
    "        '''\n",
    "        self.S = [[0]]  # Record the score\n",
    "        self.T = [[0]]  # Record the orientation\n",
    "        maxscore = 0\n",
    "        for j in range(1, len(self.seq2)+1):\n",
    "            self.S[0].append(0)\n",
    "            self.T[0].append(0)\n",
    "        for i in range(1, len(self.seq1)+1):\n",
    "            self.S.append([0])\n",
    "            self.T.append([0])\n",
    "        for i in range(0, len(self.seq1)):\n",
    "            for j in range(len(self.seq2)):\n",
    "                s1 = self.S[i][j] + self.score_pos(self.seq1[i], self.seq2[j])\n",
    "                s2 = self.S[i][j+1] + self.gap\n",
    "                s3 = self.S[i+1][j] + self.gap\n",
    "                b = max(s1, s2, s3)\n",
    "                if b <= 0:\n",
    "                    self.S[i+1].append(0)\n",
    "                    self.T[i+1].append(0)\n",
    "                else:\n",
    "                    self.S[i+1].append(b)\n",
    "                    self.T[i+1].append(PairWiseAligner.max3t(s1, s2, s3))\n",
    "                    if b > maxscore:\n",
    "                        maxscore = b\n",
    "        return maxscore\n",
    "\n",
    "    def recover_align_global(self):\n",
    "        '''Get the content of global alignment'''\n",
    "        res = [\"\", \"\"]\n",
    "        i = len(self.seq1)\n",
    "        j = len(self.seq2)\n",
    "        while i > 0 or j > 0:\n",
    "            if self.T[i][j] == 1:\n",
    "                res[0] = self.seq1[i-1] + res[0]\n",
    "                res[1] = self.seq2[j-1] + res[1]\n",
    "                i -= 1\n",
    "                j -= 1\n",
    "            elif self.T[i][j] == 3:\n",
    "                res[0] = self.gapSymbol + res[0]\n",
    "                res[1] = self.seq2[j-1] + res[1]\n",
    "                j -= 1\n",
    "            else:\n",
    "                res[0] = self.seq1[i-1] + res[0]\n",
    "                res[1] = self.gapSymbol + res[1]\n",
    "                i -= 1\n",
    "        return res\n",
    "\n",
    "    def recover_align_local(self):\n",
    "        '''Get the content of local alignment'''\n",
    "        res = [\"\", \"\"]\n",
    "        i, j = PairWiseAligner.max_mat(self.S)\n",
    "        while self.T[i][j] > 0:\n",
    "            if self.T[i][j] == 1:\n",
    "                res[0] = self.seq1[i-1] + res[0]\n",
    "                res[1] = self.seq2[j-1] + res[1]\n",
    "                i -= 1\n",
    "                j -= 1\n",
    "            elif self.T[i][j] == 3:\n",
    "                res[0] = self.gapSymbol + res[0]\n",
    "                res[1] = self.seq2[j-1] + res[1]\n",
    "                j -= 1\n",
    "            elif self.T[i][j] == 2:\n",
    "                res[0] = self.seq1[i-1] + res[0]\n",
    "                res[1] = self.gapSymbol + res[1]\n",
    "                i -= 1\n",
    "        return res\n",
    "\n",
    "    def is_legal_matrix(self, matrix):\n",
    "        '''Check whether the dict-form of matrix is legal or not'''\n",
    "        if not isinstance(matrix, dict):\n",
    "            print(\"Not a Dict\", type(matrix))\n",
    "            return False\n",
    "        else:\n",
    "            for key, data in matrix.items():\n",
    "                if len(key) != 2:\n",
    "                    print(\"Invalid Key\")\n",
    "                    return False\n",
    "                else:\n",
    "                    try:\n",
    "                        int(data)\n",
    "                    except Exception:\n",
    "                        print(\"Invalid Data\")\n",
    "                        return False\n",
    "            return True\n",
    "\n",
    "    def new_matrix(self, pairs, value):\n",
    "        '''Create new matrix via input'''\n",
    "        self.newMatrix[tuple(pairs)] = value\n",
    "        assert self.is_legal_matrix(self.newMatrix), \"Illegal Matrix !\"\n",
    "\n",
    "    def align(self, seq1, seq2, gap=-8, matrix=False, mode='global'):\n",
    "        '''Do the alignment'''\n",
    "        if isinstance(matrix, bool):\n",
    "            self.matrix = self.defaultMatrix\n",
    "        else:\n",
    "            self.matrix = matrix\n",
    "\n",
    "        assert self.is_legal_matrix(self.matrix), \"Illegal Matrix !\"\n",
    "\n",
    "        self.seq1 = seq1\n",
    "        self.seq2 = seq2\n",
    "        self.gap = gap\n",
    "\n",
    "        if mode == 'global':\n",
    "            self.needleman_Wunsch()\n",
    "            alig = self.S[len(seq1)][len(seq2)], self.recover_align_global()\n",
    "        elif mode == 'local':\n",
    "            alig = self.smith_Waterman(), self.recover_align_local()\n",
    "        else:\n",
    "            alig = None\n",
    "\n",
    "        return alig\n",
    "\n",
    "    def look_alignment(self, align_a, align_b, start, end):\n",
    "        '''Access the detailed result of the alignment'''\n",
    "        sub_seq_a = align_a[start:end+1]\n",
    "        sub_seq_b = align_b[start:end+1]\n",
    "        return \"%s\\n%s\\n%s\" % (sub_seq_a, \"|\"*(end-start+1), sub_seq_b)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "collapsed": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix(S):\n",
      "[[  0  -8 -16 -24 -32 -40 -48 -56 -64 -72 -80]\n",
      " [ -8  -2 -10 -18 -25 -33 -33 -41 -49 -57 -65]\n",
      " [-16   0  -4 -12 -20 -27 -35 -25 -33 -41 -49]\n",
      " [-24  -8   0  -7 -11 -19 -27 -33 -21 -29 -37]\n",
      " [-32 -16  -8  11   3  -5 -13 -21 -29 -10 -18]\n",
      " [-40 -24 -10   3  11   9   1  -7 -15 -18  -4]\n",
      " [-48 -32 -18  -5   3   9  16   8   0  -8 -12]\n",
      " [-56 -40 -26 -13  -5   1   8  24  16   8   0]\n",
      " [-64 -48 -34 -21 -12  -5   0  16  28  20  12]\n",
      " [-72 -56 -42 -23 -20 -13  -8   8  20  39  31]\n",
      " [-80 -64 -50 -31 -23 -14 -15   0  12  31  45]]\n",
      "\n",
      "Matrix(T):\n",
      "[[0 3 3 3 3 3 3 3 3 3 3]\n",
      " [2 1 3 3 1 3 1 3 3 3 3]\n",
      " [2 1 1 3 3 1 3 1 3 3 3]\n",
      " [2 2 1 1 1 3 3 2 1 3 3]\n",
      " [2 2 2 1 3 3 3 3 3 1 3]\n",
      " [2 2 1 2 1 1 3 3 3 2 1]\n",
      " [2 2 2 2 2 1 1 3 3 3 2]\n",
      " [2 2 2 2 2 2 2 1 3 3 3]\n",
      " [2 2 2 2 1 1 2 2 1 3 3]\n",
      " [2 2 2 1 2 2 2 2 2 1 3]\n",
      " [2 2 2 2 1 1 1 2 2 2 1]]\n",
      "\n",
      "Score: 45\n",
      "PHSW-GP\n",
      "|||||||\n",
      "-HGWAGP\n"
     ]
    }
   ],
   "source": [
    "seq1 = \"PHSWGPHSWG\"\n",
    "seq2 = \"HGWAGPHSWG\"\n",
    "pwA = PairWiseAligner()\n",
    "alignment = pwA.align(seq1, seq2, mode='global')\n",
    "print(pwA)\n",
    "print(\"Score:\", alignment[0])\n",
    "print(pwA.look_alignment(alignment[1][0], alignment[1][1], 0, 6))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [],
   "source": [
    "from itertools import combinations\n",
    "seqs = list(combinations([\"ATAGC\", \"AACC\", \"ATGAC\"], 2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(6, ['ATAGC', 'A-ACC'])\n",
      "(18, ['ATAGC', 'ATGAC'])\n",
      "(5, ['AA-CC', 'ATGAC'])\n"
     ]
    }
   ],
   "source": [
    "for seq in seqs:\n",
    "    print(pwA.align(seq[0], seq[1], mode='global'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```py\n",
    "from Bio import Alphabet\n",
    "from Bio.SeqRecord import SeqRecord\n",
    "from Bio.Align import MultipleSeqAlignment\n",
    "from Bio.Alphabet import IUPAC\n",
    "from Bio.Seq import Seq\n",
    "seq1 = \"MHQAIFIYQIGYPLKSGYIQSIRSPEYDNW\"\n",
    "seq2 = \"MH−−IFIYQIGYALKSGYIQSIRSPEY−NW\"\n",
    "seq3 = \"MHQAIFI−QIGYALKSGY−QSIRSPEYDNW\"\n",
    "seqr1 = SeqRecord(Seq(seq1,Alphabet.Gapped(IUPAC.protein)),id=\"seq1\")\n",
    "seqr2 = SeqRecord(Seq(seq2,Alphabet.Gapped(IUPAC.protein)),id=\"seq2\")\n",
    "seqr3 = SeqRecord(Seq(seq3,Alphabet.Gapped(IUPAC.protein)),id=\"seq3\")\n",
    "alin = MultipleSeqAlignment([seqr1, seqr2, seqr3])\n",
    "print(alin)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Generate a Evolutionary/Phylogenetic Tree\n",
    "* Classic\n",
    "    * 形态学特征\n",
    "    * 生理，生化，行为习性特征\n",
    "    * 化石\n",
    "    * 缺点: 表型一般与多个遗传因素相关，结果会产生偏差；化石样品难寻\n",
    "* Molecular\n",
    "    * 利用从遗传物质中提取的信息作为特征 \n",
    "    * 即利用核酸或蛋白质序列（molecular fossil)\n",
    "    * 优点：数量大，获取容易\n",
    "\n",
    "#### Node\n",
    "* taxonomy单位\n",
    "\n",
    "#### Branch\n",
    "* node与node间关系？\n",
    "* branch长度反映当这件事发生时就存在的蛋白质与现在的蛋白质之间的距离\n",
    "\n",
    "#### Rooted Tree and Unrooted Tree\n",
    "* rooted tree: 树上物种或基因进化的时间顺序，可显示共同祖先\n",
    "* unrooted tree: 树系中代表时间视最早的共同祖先不能确定，只反映分类单元间的距离\n",
    "\n",
    "#### Application\n",
    "* 物种分类及定义\n",
    "* 功能注释\n",
    "* 法医等领域\n",
    "\n",
    "#### Distannce-Based Algorithms\n",
    "* 在距离矩阵的基础上，根据双序列比对时的差异程度建立进化树\n",
    "* 目标函数: 测量树中叶子（代表序列）之间距离的一致性，这些距离是通过序列相似性（通过序列比对）获得的\n",
    "* 序列间的距离矩阵：序列间的相似性的倒数-序列比对 \n",
    "* 已知距离矩阵，目标函数可以定义为使误差函数即树中距离和矩阵中距离的误差最小:\n",
    "$$\\text{score}(T) = \\sum_{i,j \\in S}(d_{ij}(T)-D_{ij})^2$$\n",
    "    * S是输入的序列\n",
    "    * T表示树\n",
    "    * dij(T)表示序列i和j在树中的距离\n",
    "    * Dij表示在矩阵中的距离\n",
    "* 树中节点u和v的距离，与从u到v游走的垂直距离。如果w是这两个点的最近祖先，那么u和v的距离为$d_{uw} + d_{wv}$\n",
    "* $d_{uw} = h(w) - h(u)$\n",
    "* $d_{wv} = h(w) - h(v)$ 其中h(x)表示节点在树中的高度 \n",
    "* ```NP-hard```\n",
    "\n",
    "##### Unweighted Pair Group Method Using Arithmetic Averages (UPGMA)\n",
    "> Unweighted; Pair Group; Arithmetic Average\n",
    "\n",
    "* 凝聚层次聚类算法\n",
    "* 步骤\n",
    "    * 将每个序列看成一个聚类，并设置高度为0\n",
    "    * 将距离最近的序列或聚类 (距离矩阵中最小的数值) 合并成内部节点，并设置在树中高度为序列距离的一半\n",
    "    * 这些序列在下回迭代中将会被看作一个聚类，距离将会是与剩余的序列节点距离的平均值\n",
    "    * 具以上更新距离矩阵\n",
    "\n",
    "$$\\dfrac{1}{\\left| A \\right|.\\left| B \\right|} \\sum_{i \\in A} \\sum_{j \\in B} D_{ij} $$\n",
    "\n",
    "\n",
    "$$D(A\\cup B, X) = \\dfrac{\\left| A \\right|.D(A,X) + \\left|B \\right|.D(B,X)}{\\left| A \\right|+\\left|B\\right|}$$\n",
    "\n",
    "\n",
    "$$D(A\\cup B, X) = \\dfrac{D(A,X) + D(B,X)}{2}$$\n",
    "#### Maximum Parsimony\n",
    "* 包括了通过确定可以解释序列最小可变性的必须突变的方法\n",
    "* 基本假设：生物序列总是采用某种“最节约成本”或“最经济”的方法完成进化\n",
    "* 计算量小,适用于大规模系统树构建。软件有MEGA和phylip \n",
    "#### Statistical/Bayesian Methods/最大似然法？\n",
    "* 定义不同类型突变发生率的概率模型，基于次对进化树进行评分, 根据假设的模型搜索最 可能解释序列的树\n",
    "* 适用于探索远缘序列间的关系。软件有DNAML和DNALK\n",
    "\n",
    "#### Use Binary Tree to represent Phylogenetic Tree\n",
    "* 假设：所有的内部节点都有两个分支\n",
    "* 每个内部节点包含了高度信息（数值型)\n",
    "* 每个叶子包含了序列的索引（整数型）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
